/*
 * chtbl.c
 *
 *  Created on: Jun 10, 2012
 *      Author: Rui Carlos Lorenzetti da Silva
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "list.h"
#include "chtbl.h"

/**
 * Inicializa Chained Hashtable
 */

int chtbl_init(CHTbl *htbl, int buckets, int (*h)(const void *key),
		int (*match)(const void *key1, const void *key2),
		void (*destroy)(void*data)) {

	int i;

	/**
	 * Aloca espaço para hashtable
	 */

	if ((htbl->table = (List *) malloc(buckets * sizeof(List))) == NULL)
		return -1;

	/**
	 * Inicializa os bucktes
	 */

	htbl->buckets = buckets;

	for (i = 0; i < htbl->buckets; i++)
		list_init(&htbl->table[i], destroy);

	/**
	 * Encapsula as funções.
	 */

	htbl->h = h;
	htbl->match = match;
	htbl->destroy = destroy;

	/**
	 * Inicializa a quantidade de termos.
	 */

	htbl->size = 0;

	return 0;

}

/**
 * Destroe a hashTable
 */

void chtbl_destroy(CHTbl *htbl) {

	int i;

	/**
	 * Destroe cada bucket
	 */

	for (i = 0; i < htbl->buckets; i++) {

		list_destroy(&htbl->table[i]);

	}

	/**
	 * destroe a hashtable
	 */

	free(htbl->table);

	/**
	 * Limpando a estrutura por precaução.
	 */

	memset(htbl, 0, sizeof(CHTbl));

	return;

}
/**
 * Cria a pesquisa em cima da hashtable
 * retorna 0 caso tenha encontrado o valor.
 * Usado internamente a chained hash table.
 */

int chtbl_lookup_int(const CHTbl *htbl, void *data) {

	ListElmt *element;

	int bucket;

	bucket = htbl->h(data) % htbl->buckets;

	for (element = list_head((List *)&htbl->table[bucket]); element != NULL;element = list_next(element)) {

		if (htbl->match(data, list_data(element))) {
			return 0;

		}

	}

	return -1;

}

/**
 * Insere dado dentro da Hashtable, caso já exista o dado é colocado dentro da lista do bucket.
 */

int chtbl_insert(CHTbl *htbl, const void *data) {

	void *temp;

	int bucket, retval;

	/**
	 * Adicionando para a lista
	 */

	temp = (void *) data;

	/**
	 * Realiza o hash
	 */
	bucket = htbl->h(data) % htbl->buckets;

	if (chtbl_lookup_int(htbl, &temp) == 0) {
		bucket = htbl->h(data) % htbl->buckets;
		list_ins_next(&htbl->table[bucket], NULL, data);
		htbl->size++;
		return 1;
	}

	/**
	 * Insere um novo dado.
	 */

	if ((retval = list_ins_next(&htbl->table[bucket], NULL, data)) == 0)
		htbl->size++;

	return retval;

}

/**
 * Remove o dado de dentro da hashtable
 */
int chtbl_remove(CHTbl *htbl, void **data) {

	ListElmt *element, *prev;

	int bucket;

	/**
	 * Criando chave hash
	 */

	bucket = htbl->h(*data) % htbl->buckets;

	/**
	 * Procurando dado dentro do bucket
	 */

	prev = NULL;

	for (element = list_head((List *)&htbl->table[bucket]); element != NULL;
			element = list_next(element)) {

		if (htbl->match(*data, list_data(element))) {

			/**
			 * Removendo o dado encontrado.
			 */

			if (list_rem_next(&htbl->table[bucket], prev, data) == 0) {

				htbl->size--;
				return 0;

			}

			else {

				return -1;

			}

		}

		prev = element;

	}

	/**
	 * Retornando que o dado não foi encontrado.
	 */

	return -1;

}

/**
 * Cria a pesquisa em cima da hashtable
 */

List * chtbl_lookup(const CHTbl *htbl, void *data, void (*destroy)(void*data)) {

	ListElmt *element;

	int bucket;

	/**
	 * Cria a chave de hash
	 */

	bucket = htbl->h(data) % htbl->buckets;

	/**
	 * Procurando dado no hash
	 */
	List* list_resposta = calloc(1, sizeof(List));
	list_init(list_resposta, destroy);

	for (element = list_head((List *)&htbl->table[bucket]); element != NULL;
			element = list_next(element)) {

		if (htbl->match(data, list_data(element)) == 0) {

			/**
			 * Adicionando elemento encontrado dentro da lista de resposta
			 */
			data = list_data(element);
			list_ins_next(list_resposta, list_resposta->head, data);

		}
	}
	return list_resposta;

}
